/*
 * =====================================================================================
 *
 *       Filename:  EntityConverter.cpp
 *
 *    Description:  Convert Reflex entities into Xonotic entities
 *
 *        Version:  1.0 
 *        Created:  06/05/2017 07:15:25 PM
 *       Revision:  none
 *       Compiler:  gcc
 *
 *         Author:  suhrke@protonmail.com
 *
 * =====================================================================================
 */

#include "EntityConverter.hpp"

#include <exception>
#include <fstream>
#include <iomanip>
#include <iostream> 
#include <iterator>
#include <sstream>




/*-----------------------------------------------------------------------------
 *  PUBLIC
 *-----------------------------------------------------------------------------*/



EntityConverter::EntityConverter (const std::string &entityMapFile) 
                                 : OFFSET_PLAYER(32.0), 
                                   OFFSET_PICKUP(2.0), 
                                   BRIGHTNESS_ADJUST(50.0),
                                   OUTPUT_PRECISION(6)
{
  //MUST RUN extractMapInfo method after this constructor
  haveMapInfo_ = false; 
  // game modes default to enabled
  ws_.cts  = true;
  ws_.ctf  = true;
  ws_.ffa  = true;
  ws_.tdm  = true;
  ws_.duel = true;

  mapEntities (entityMapFile);
}



EntityConverter::EntityConverter (const std::string &entityMapFile, 
    const std::string &reflexMapFile) : OFFSET_PLAYER(32.0), 
    OFFSET_PICKUP(2.0), BRIGHTNESS_ADJUST(50.0), OUTPUT_PRECISION(6)
{
  haveMapInfo_ = false;
  // game modes default to enabled
  ws_.cts  = true;
  ws_.ctf  = true;
  ws_.ffa  = true;
  ws_.tdm  = true;
  ws_.duel = true;

  mapEntities (entityMapFile);
  
  // Pre-scan for info needed by converter
  std::ifstream fin;
  fin.open (reflexMapFile);

  if (fin.is_open()) {
    //Extract the source type of targets (teleporters or jump pads)
    std::string line;
    while (std::getline(fin, line)) {
      extractFromEntity (line, fin);
    }
  }
  else {
    throw std::ios::failure ("Error: EntityConverter failed to open .map file " + reflexMapFile);
  }

  fin.close();

  if (haveRequiredMappings())
    haveMapInfo_ = true;
}



void
EntityConverter::extractMapInfo (std::queue<std::vector<std::string>> entities)
{
  if (haveMapInfo_) {
    std::cerr << "Map info already extracted, doing nothing" << std::endl;
  }
  else {
    while ( ! entities.empty()) {
      std::vector<std::string> entity (entities.front());
      entities.pop();

      std::stringstream ss;
      std::copy (entity.begin(), entity.end(), 
          std::ostream_iterator<std::string> (ss, "\n"));

      std::string nextLine;
      if (getline(ss, nextLine)) {
        extractFromEntity(nextLine, ss);
      }
    }
  }

  if (haveRequiredMappings())
    haveMapInfo_ = true;
}



void
EntityConverter::extractMapInfo (const std::vector<std::vector<std::string>> &entities)
{
  if (haveMapInfo_) {
    std::cerr << "Map info already extracted, doing nothing" << std::endl;
  }
  else {
    std::vector<std::vector<std::string>>::const_iterator it;
    for (it=entities.begin(); it!=entities.end(); ++it) {
      std::vector<std::string> entity (*it);

      std::stringstream ss;
      std::copy (entity.begin(), entity.end(), 
          std::ostream_iterator<std::string> (ss, "\n"));

      std::string nextLine;
      if  (getline (ss, nextLine)) {
        extractFromEntity (nextLine, ss);
      }
    }
  }

  haveMapInfo_ = true;
}



std::vector<std::string> 
EntityConverter::convert (const std::vector<std::string> &lines)
{
  if  (haveMapInfo_)
  {
    std::string attribute;
    std::string trash; //unused tokens

    std::string type;
    if (lines.size() < 1) {
      throw std::runtime_error ( 
          makeErrorMessage( "error: empty entity cannot be converted", lines));
    }
    // second token is the type
    std::istringstream iss(lines[0]);
    if ( ! (iss >> trash >> type)) { 
      throw std::runtime_error( 
          makeErrorMessage ("error: type is required", lines));
    }

    // If worldspawn, first reenable all gamemodes
    // then check worldspawn for disabled modes
    // then RETURN EMPTY VECTOR
    if      (type == "WorldSpawn") {
      ws_.cts  = true;
      ws_.ctf  = true;
      ws_.ffa  = true;
      ws_.tdm  = true;
      ws_.duel = true;

      // Each worldspawn can specify modes enabled/disabled
      for (int i = 1; i < lines.size(); ++i) {
        if      (lines[i].find("modeRace 0") != std::string::npos) {
          ws_.cts = false;
        }
        else if (lines[i].find("modeCTF 0") != std::string::npos) {
          ws_.ctf = false;
        }
        else if (lines[i].find("modeFFA 0") != std::string::npos) {
          ws_.ffa = false;
        }
        else if (lines[i].find("modeTDM 0") != std::string::npos) {
          ws_.tdm = false;
        }
        else if (lines[i].find("mode1v1 0") != std::string::npos) {
          ws_.duel = false;
        }
      }

    }
    else if (type == "Pickup") { 
      return convertPickup (lines);
    } 
    else if (type == "PlayerSpawn") { 
      return convertPlayerSpawn (lines);
    } 
    else if (type == "JumpPad") {
      return convertJumpPad (lines);
    }
    else if (type == "Teleporter") { 
      return convertTeleporter (lines);
    } 
    else if (type == "Target") {
      return convertTarget (lines);
    } 
    else if (type == "RaceStart") {
      return convertRaceStart (lines);
    } 
    else if (type == "RaceFinish") {
      return convertRaceFinish (lines);
    } 
    else if (type == "PointLight") {
      return convertPointLight (lines);
    } 
  }
  else {
    throw std::runtime_error ( 
        makeErrorMessage ("error: Map info must be extracted  prior to conversion", lines));
  }

  // If unsupported entity, return empty vector
  std::vector<std::string> empty;
  return empty;
}




/*-----------------------------------------------------------------------------
 *  PROTECTED
 *-----------------------------------------------------------------------------*/



std::vector<std::string>
EntityConverter::convertPickup (const std::vector<std::string> &lines) const
{
  std::vector<std::string> convertedLines;
  //can ignore angle of pickups in xonotic format
  std::string coords[3] = {"0.0", "0.0", "0.0"};
  std::string pickupID; 
  std::string trash;
  bool havePickupID = false;

  if  (lines.size() < 2) {
    throw std::runtime_error ( 
        makeErrorMessage ("error: Pickup entity requires minimum of two lines (type and ID)", lines));
  }

  for (int i = 1; i < lines.size(); i++) {
    std::string type = getAttributeType (lines[i]);
    if ( type == "position") {
      std::istringstream iss (lines[i]);  
      // Vector3 position coord0 coord1 coord2
      if ( ! (iss >> trash >> trash >> 
              coords[0] >> coords[1] >> coords[2])) { 
        throw std::runtime_error ( 
            makeErrorMessage ("error: Invalid Pickup position", lines));
      } 
    } 
    else if (type == "pickupType") {
      std::istringstream iss (lines[i]);  
      // UInt8 pickupType ID
      if ( ! (iss >> trash >> trash >> pickupID)) { 
        throw std::runtime_error ( 
            makeErrorMessage ("error: Format of Pickup ID line is invalid", lines));
      }
      havePickupID = true;
    }
  }
    
  if (havePickupID) {
    auto pickupIter = entityMap_.find (pickupID);
    if ( pickupIter == entityMap_.end()) {
      throw std::runtime_error( 
          makeErrorMessage ("error: Pickup ID is invalid", lines));
    }
    std::stringstream pickupStream;
    pickupStream << "\"classname\" \"" <<  pickupIter->second << "\"" << std::endl;
    convertedLines.push_back (pickupStream.str());
    // coordinates reordered to x, z, y
    std::stringstream positionStream;
    positionStream << "\"origin\" \"" << coords[0] << " " << coords[2] << " " <<
            offset (coords[1], OFFSET_PICKUP) << "\"" << std::endl;
    convertedLines.push_back (positionStream.str());
    return convertedLines;
  }
  else {
    throw std::runtime_error ( 
        makeErrorMessage ("error: Pickup ID was not in the entity", lines));
  }
}


 
/*
 *--------------------------------------------------------------------------------------
 *       Class:  EntityConverter
 *      Method:  EntityConverter :: convertPlayerSpawn
 *       Notes:    REFLEX
 *               -Optionally includes angle, team indicator, and 0 or more game
 *                mode indicators (defaults to all modes enabled so this line 
 *                is used to disable a mode. eg Bool8 modeRace 0)
 *--------------------------------------------------------------------------------------
 */
std::vector<std::string>
EntityConverter::convertPlayerSpawn (const std::vector<std::string> &lines) const
{
  std::vector<std::string> convertedLines;
  // Requires position coordinate 
  std::string coords[3] = {"0.0", "0.0", "0.0"};
  // Requires an angle, default to 0 degrees (floating point)
  std::string angle("0.0");
  // 1-2 for corresponding team, 0 for deathmatch spawn
  int team = 0; 
  std::string trash;
  bool isModeRace = ws_.cts;
  bool isModeCtf  = ws_.ctf;
  bool isModeTdm  = ws_.tdm;
  bool isModeFfa  = ws_.ffa;
  bool isModeDuel = ws_.duel;
  

  
  for (int i = 1; i < lines.size(); i++) {
    std::string type = getAttributeType(lines[i]);
    if (type == "position") {
      std::istringstream iss (lines[i]);  
      // Vector3 position coord0 coord1 coord2
      if ( ! (iss >> trash >> trash >> 
              coords[0] >> coords[1] >> coords[2])) { 
        throw std::runtime_error ( 
            makeErrorMessage ("error: Invalid PlayerSpawn position", lines));
      } 
    } 
    else if (type == "angles") {
      std::istringstream iss (lines[i]);  
      // UInt8 pickupType ID
      if ( ! (iss >> trash >> trash >> angle)) { 
        throw std::runtime_error ( 
            makeErrorMessage ("error: Invalid PlayerSpawn angle", lines));
      }
    }
    // Bool8 modeX 0 indicates this spawn is not for game mode X
    else if (type == "modeRace") {
      isModeRace = false; 
    }
    else if (type == "modeCTF") {
      isModeCtf = false; 
    }
    else if (type == "modeTDM") {
      isModeTdm = false;
    }
    else if (type == "modeFFA") {
      isModeFfa = false;
    }
    else if (type == "mode1v1") {
      isModeDuel = false; 
    }
    else if (type == "teamA") {
      team = 2; // Bool8 teamA 0 indicates teamB only
    }
    else if (type == "teamB") {
      team = 1; // Bool8 teamB 0 indicates teamA only
    }
  }

  if (isModeCtf || isModeTdm || isModeFfa || isModeDuel) {
    std::stringstream ss;
    std::map<std::string, std::string>::const_iterator it;
    switch (team) {
      case 0:
        convertedLines.push_back ("\"classname\" \"info_player_deathmatch\"\n");
        break;
      case 1: 
        it = entityMap_.find ("team1");
        ss  << "\"classname\" \"" << it->second << "\"" << std::endl;
        convertedLines.push_back (ss.str());
        break;
      case 2:
        it = entityMap_.find ("team2");
        ss  << "\"classname\" \"" << it->second << "\"" << std::endl;
        convertedLines.push_back (ss.str());
        break;
    }
  }
  else {
    convertedLines.push_back ("\"classname\" \"info_player_start\"\n");
  }

  std::stringstream positionStream;
  // coordinates reordered to x, z, y
  positionStream << "\"origin\" \"" << coords[0] << " " << coords[2] << " " <<
    offset (coords[1], OFFSET_PLAYER) << "\"" << std::endl;
  convertedLines.push_back (positionStream.str());
  std::stringstream angleStream;
  angleStream << "\"angle\" \"" << adjustAngleForHandedness(angle) << "\"" << std::endl;
  convertedLines.push_back (angleStream.str());
  return convertedLines;

}



std::vector<std::string>
EntityConverter::convertJumpPad (const std::vector<std::string> &lines) const
{
  std::vector<std::string> convertedLines;
  std::string targetName;
  std::string trash;

  if (lines.size() < 2) {
    throw std::runtime_error( 
        makeErrorMessage ("error: JumpPad entity requires minimum of two lines (type and target)", lines));
  }
  std::istringstream iss (lines[1]);  
  // String32 target targetName
  if ( ! (iss >> trash >> trash >> targetName)) { 
    throw std::runtime_error( 
        makeErrorMessage ("error: Invalid JumpPad target", lines));
  }
  
  convertedLines.push_back ("\"classname\" \"trigger_push\"\n");
  std::stringstream oss;
  oss << "\"target\" \"" << targetName << "\"" << std::endl;
  convertedLines.push_back (oss.str());
  return convertedLines;
}



std::vector<std::string>
EntityConverter::convertTeleporter (const std::vector<std::string> &lines) const
{
  std::vector<std::string> convertedLines;
  std::string targetName;
  std::string trash;

  if (lines.size() < 2) {
    throw std::runtime_error ( 
        makeErrorMessage ("error: Teleport entity requires minimum of two lines (type and target)", lines));
  }
  std::istringstream iss (lines[1]);  
  // String32 target targetName
  if ( ! (iss >> trash >> trash >> targetName)) { 
    throw std::runtime_error ( 
        makeErrorMessage ("error: Invalid Teleport target", lines));
  }
  
  convertedLines.push_back ("\"classname\" \"trigger_teleport\"\n");
  std::stringstream oss;
  oss << "\"target\" \"" << targetName << "\"" << std::endl;
  convertedLines.push_back (oss.str());
  return convertedLines;
}



std::vector<std::string>
EntityConverter::convertTarget (const std::vector<std::string> &lines) const
{
  std::vector<std::string> convertedLines;
  //position and name required, angles optional
  std::string coords[3] = {"0.0", "0.0", "0.0"};
  std::string targetName;
  std::string angle = "0.0";
  std::string trash;
  bool haveName = false;


  if (lines.size() < 3) {
    throw std::runtime_error ( 
        makeErrorMessage ("error: Target entity requires minimum of two lines (type and name)", lines));
  }

  for (int i = 1; i < lines.size(); i++) {
    std::string type = getAttributeType (lines[i]);
    if (type == "position") {
      std::istringstream iss (lines[i]);  
      // Vector3 position coord0 coord1 coord2
      if ( ! (iss >> trash >> trash >> 
              coords[0] >> coords[1] >> coords[2])) { 
          throw std::runtime_error ( 
              makeErrorMessage ("error: Invalid Target position", lines));
      } 
    } 
    else if (type == "name") {
      std::istringstream iss (lines[i]);  
      // UInt8 name uniqueName
      if ( ! (iss >> trash >> trash >> targetName)) { 
        throw std::runtime_error ( 
            makeErrorMessage ("error: Invalid Target \"target\"", lines));
      }
      haveName = true;
    }
    else if (type == "angles") {
      std::istringstream iss (lines[i]);  
      // Vector3 angles angle notapplicable notapplicable
      if ( ! (iss >> trash >> trash >> angle)) { 
        throw std::runtime_error ( 
            makeErrorMessage ("error: Invalid Target angle", lines));
      }
    }

  }
    
  if (haveName) {
    auto targetIter = targetMap_.find (targetName);
    if (targetIter == targetMap_.end()) {
      //std::cerr << makeErrorMessage ("End-game camera Target is not a supported feature in id Tech games.  Skipping", lines);

      std::vector<std::string> empty;
      return empty;
    }
    if (targetIter->second == "Teleporter") {
      convertedLines.push_back ("\"classname\" \"misc_teleporter_dest\"\n");
      // coordinates reordered to x, z, y
      // teleporter height is OFFSET
      std::stringstream oss;
      oss << "\"origin\" \"" << coords[0] << " " << coords[2] << " " <<
        offset (coords[1], OFFSET_PLAYER) << "\"" << std::endl;
      convertedLines.push_back ( oss.str());
    }
    else if ( targetIter->second  == "JumpPad") {
      convertedLines.push_back ("\"classname\" \"target_position\"\n");
      // coordinates reordered to x, z, y
      std::stringstream oss;
      oss << "\"origin\" \"" << coords[0] << " " << coords[2] << " " <<
        coords[1] << "\"" << std::endl;
      convertedLines.push_back (oss.str());
    }
    std::stringstream targetStream;
    targetStream << "\"targetname\" \"" << targetName << "\"" << std::endl;
    convertedLines.push_back (targetStream.str());
    
    // write angle every time
    std::stringstream angleStream;
    angleStream << "\"angle\" \"" << adjustAngleForHandedness (angle) << "\"" << std::endl;
    convertedLines.push_back( angleStream.str());
    return convertedLines;
  }
  else {
    throw std::runtime_error ( 
        makeErrorMessage ("error: \"target\" was not found in this Target entity", lines));
  }

}



std::vector<std::string>
EntityConverter::convertRaceStart (const std::vector<std::string> &lines) const
{
  std::vector<std::string> convertedLines;
  convertedLines.push_back ("\"classname\" \"trigger_race_checkpoint\"\n");
  convertedLines.push_back ("\"targetname\" \"cp1\"\n");
  // While 0 is finish in Race, in CTS, checkpoints numbered from 0 to n
  convertedLines.push_back ("\"cnt\" \"0\"\n");
  return convertedLines;
}



std::vector<std::string>
EntityConverter::convertRaceFinish (const std::vector<std::string> &lines) const
{
  std::vector<std::string> convertedLines;
  convertedLines.push_back ("\"classname\" \"trigger_race_checkpoint\"\n");
  convertedLines.push_back ("\"targetname\" \"finish\"\n");
  // While 0 is finish in Race, in CTS, checkpoints numbered from 0 to n
  convertedLines.push_back ("\"cnt\" \"1\"\n");
  return convertedLines;
}



std::vector<std::string> 
EntityConverter::convertPointLight (const std::vector<std::string> &lines) const
{
  std::vector<std::string> convertedLines;
  //position and intensity required, color optional
  std::string coords[3] = {"0.0", "0.0", "0.0"};
  //default to a typical value
  std::string intensity = "1.0";
  //color is hex 8 digits
  //default to white if no color specified
  std::string color = "ff000000";
  std::string trash;
  bool haveColor = false;


  if (lines.size() < 2) {
    throw std::runtime_error ( 
        makeErrorMessage ("error: PointLight entity requires at least one line (type)", lines));
  }

  for (int i = 1; i < lines.size(); i++) {
    std::string type = getAttributeType(lines[i]);
    if (type == "position") {
      std::istringstream iss (lines[i]);  
      // Vector3 position coord0 coord1 coord2
      if ( ! (iss >> trash >> trash >> 
              coords[0] >> coords[1] >> coords[2])) { 
          throw std::runtime_error ( 
              makeErrorMessage ("error: Invalid PointLight position", lines));
      } 
    } 
    else if (type == "intensity") {
      std::istringstream iss (lines[i]);  
      // Float intensity validFloat
      if ( ! (iss >> trash >> trash >> intensity)) { 
        throw std::runtime_error ( 
            makeErrorMessage ("error: Invalid PointLight intensity", lines));
      }
    }
    else if (type == "color") {
      std::istringstream iss (lines[i]);  
      // ColourXRGB32 color eightDigitHexValue
      if ( ! (iss >> trash >> trash >> color)) { 
        throw std::runtime_error ( 
            makeErrorMessage ("error: Invalid PointLight color", lines));
      }
      haveColor = true;
    }

  }


    convertedLines.push_back ("\"classname\" \"light\"\n");
    // coordinates reordered to x, z, y
    std::stringstream positionStream;
    positionStream << "\"origin\" \"" << coords[0] << " " << coords[2] << " " <<
           coords[1] << "\"" << std::endl;
    convertedLines.push_back (positionStream.str());
    // convert intensity
    std::stringstream intensityStream;
    intensityStream << "\"light\" \"" << adjustBrightness (intensity) << "\"\n";
    convertedLines.push_back (intensityStream.str());
    
    float red;
    float green;
    float blue;
    // Convert 32bit hex RGBA value (ALPHA ALWAYS FULL) into RGB values
    hexToRGB (color, red, green, blue);

    std::stringstream colorStream;
    colorStream << "\"_color\" \"" << red << " " << green << " " << blue << "\"" << std::endl;
    convertedLines.push_back (colorStream.str());

    return convertedLines;
}




std::string 
EntityConverter::getAttributeType (const std::string &line) const
{
  std::string type;
  std::string dataType;
  std::istringstream iss (line);  
  if ( ! (iss >> dataType >> type)) {
    return std::string();
  } 

  return type;
}



void
EntityConverter::mapEntities (const std::string &mapFile)
{
  std::ifstream fin;
  fin.open (mapFile);

  if (fin.is_open()) {
    //Read Reflex Entity Mapping file contents into pickup map
    std::string line;
    while (std::getline (fin, line)) {
      std::istringstream iss (line);
      // Reflex ID corresponds to xonotic pickup name
      std::string id;
      std::string pickup;
      if ( ! (iss >> id >> pickup)) { 
        throw std::runtime_error ("format error in Pickup .pck file " + mapFile);
      }
      entityMap_.insert (std::pair<std::string, std::string> (id, pickup));
    }
  }
  else {
    throw std::ios::failure ("Error: EntityConverter failed to open Reflex Entity Mapping file");
  }
  fin.close();

}



bool 
EntityConverter::haveRequiredMappings()
{
  std::vector<std::string> required = { "team1", 
                                        "team2" };
  for (std::string id : required) {
    auto pickupIter = entityMap_.find (id);
    if ( pickupIter == entityMap_.end()) { 
      throw std::runtime_error ("error: Missing required entity mappings");
      return false;
    }
  }

  return true;
}



void
EntityConverter::extractFromEntity (const std::string &line, std::istream &is)
{
  std::string trash;
  std::string targetName;
  std::string nextLine;
  if (line.find("type Teleporter") != std::string::npos) {
    std::getline (is, nextLine);
    std::istringstream iss (nextLine);
    if ( ! (iss >> trash >> trash >> targetName)) { 
      throw std::runtime_error ("Format error in .map file");
    }
    targetMap_.insert (std::pair<std::string, std::string>(targetName, "Teleporter"));
  }
  else if (line.find ("type JumpPad") != std::string::npos) {
    std::getline (is, nextLine);
    std::istringstream iss (nextLine);
    if ( ! (iss >> trash >> trash >> targetName)) { 
      throw std::runtime_error ( "Format error in .map file");
    }
    targetMap_.insert (std::pair<std::string, std::string> (targetName, "JumpPad"));
  }
}



std::string 
EntityConverter::offset (const std::string &value, const float amount) const
{
  std::istringstream iss (value);
  float c;
  iss >> c;
  c += amount;

  std::stringstream ss;
  ss << std::fixed << std::fixed << std::setprecision (OUTPUT_PRECISION) << c;
  return ss.str();
}



std::string
EntityConverter::adjustAngleForHandedness (const std::string &angle) const
{
  std::istringstream iss (angle);
  float a;
  iss >> a;
  a = -a + 90.0;

  std::stringstream ss;
  ss << std::fixed << std::fixed << std::setprecision (OUTPUT_PRECISION) << a;
  return ss.str();
}



void 
EntityConverter::hexToRGB (const std::string &hex, float &r, float &g, float &b) const
{
  unsigned int value;
  std::stringstream ss;
  ss << std::hex << hex;
  ss >> value;

  // BYTE ORDER IS ARGB
  // Alpha value is always full -> can be ignored safely
  // Get each value and normalize
  r = ((value >> 16) & 0xFF) / 255.0;
  g = ((value >> 8)  & 0xFF) / 255.0;
  b = ((value)       & 0xFF) / 255.0;
}



int 
EntityConverter::adjustBrightness (const std::string &value) const
{
  float inputBright;
  std::stringstream ss (value);
  ss >> inputBright;

  return static_cast<int>(inputBright * BRIGHTNESS_ADJUST);
}




std::string 
EntityConverter::makeErrorMessage (const std::string message, 
                                    const std::vector<std::string> entity) const
{
  std::stringstream ss;
  ss << std::endl << message <<  ":" << std::endl;

  std::vector<std::string>::const_iterator it;
  for (it=entity.begin(); it!=entity.end(); ++it) {
    ss << *it << std::endl; 
  }

  ss << std::endl;

  return ss.str();
}




/*-----------------------------------------------------------------------------
 *  PRIVATE
 *-----------------------------------------------------------------------------*/



// DEBUG
void 
EntityConverter::printMapping() const
{
  std::cout << std::endl << "Reflex pickup ID mapped to Xonotic pickup names: " << std::endl; 
  std::map<std::string, std::string>::const_iterator it;
  for (it=entityMap_.begin(); it!=entityMap_.end(); ++it)
    std::cout << it->first << " => " << it->second << std::endl;
}

// DEBUG
void 
EntityConverter::printTargetSources() const
{
  std::cout << std::endl << "Target and Sources: " << std::endl;
  std::map<std::string, std::string>::const_iterator it;
  for (it=targetMap_.begin(); it!=targetMap_.end(); ++it)
    std::cout << it->first << " => " << it->second << std::endl;
}

